﻿// Copyright © 2011 Vladislav Borovikov
// http://vividkit.codeplex.com/

namespace VividKit.Controls
{
	using System;
	using System.Collections;
	using System.Collections.Specialized;
	using System.Linq;
	using System.Windows;
	using System.Windows.Markup;
	using Microsoft.Phone.Controls;
	using Microsoft.Phone.Shell;

	[ContentProperty("Items")]
	public class PageBar : DependencyObject
	{
		private readonly ApplicationBar applicationBar;

		#region Page (DependencyProperty)

		private static readonly DependencyProperty PageProperty =
					DependencyProperty.Register("Page", typeof(PhoneApplicationPage), typeof(PageBar),
					  new PropertyMetadata(null));

		/// <summary>
		/// A description of the property.
		/// </summary>
		internal PhoneApplicationPage Page
		{
			get { return (PhoneApplicationPage)GetValue(PageProperty); }
			private set { SetValue(PageProperty, value); }
		}

		#endregion Page (DependencyProperty)

		#region Instance (Attached DependencyProperty)

		public static readonly DependencyProperty InstanceProperty =
			DependencyProperty.RegisterAttached("Instance", typeof(PageBar), typeof(PageBar), new PropertyMetadata(HandleInstancePropertyChanged));

		public static PageBar GetInstance(DependencyObject o)
		{
			return (PageBar)o.GetValue(InstanceProperty);
		}

		public static void SetInstance(DependencyObject o, PageBar value)
		{
			o.SetValue(InstanceProperty, value);
		}

		private static void HandleInstancePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			var page = d as PhoneApplicationPage;
			if (page != null)
			{
				page.ApplicationBar = null;
				var pageBar = e.NewValue as PageBar;
				if (pageBar != null)
				{
					pageBar.Page = page;
					page.ApplicationBar = pageBar.applicationBar;
				}
			}
		}

		#endregion Instance (Attached DependencyProperty)

		#region Items (DependencyProperty)

		public static readonly DependencyProperty ItemsProperty =
					DependencyProperty.Register("Items", typeof(PageBarItemCollection), typeof(PageBar),
					new PropertyMetadata(null, HandleItemsPropertyChanged));

		/// <summary>
		/// Gets or sets the items.
		/// </summary>
		/// <value>
		/// The buttons and the menu items.
		/// </value>
		public PageBarItemCollection Items
		{
			get { return (PageBarItemCollection)GetValue(ItemsProperty); }
		}

		protected virtual void OnItemsChanged(DependencyPropertyChangedEventArgs e)
		{
			this.applicationBar.Buttons.Clear();
			this.applicationBar.MenuItems.Clear();

			var oldItems = e.OldValue as PageBarItemCollection;
			if (oldItems != null)
			{
				oldItems.CollectionChanged -= HandleItemsCollectionChanged;
			}

			var newItems = e.NewValue as PageBarItemCollection;
			if (newItems != null)
			{
				newItems.SetOwner(this);
				newItems.CollectionChanged += HandleItemsCollectionChanged;
			}
		}

		private static void HandleItemsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			((PageBar)d).OnItemsChanged(e);
		}

		private void HandleItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
		{
			if (e.Action == NotifyCollectionChangedAction.Reset)
			{
				this.applicationBar.Buttons.Clear();
				this.applicationBar.MenuItems.Clear();
			}

			if (e.Action == NotifyCollectionChangedAction.Remove || e.Action == NotifyCollectionChangedAction.Replace)
			{
				foreach (var button in e.OldItems.OfType<PageBarButton>())
					this.applicationBar.Buttons.Remove(button.InternalButton);

				foreach (var menuItem in e.OldItems.OfType<PageBarMenuItem>())
					this.applicationBar.MenuItems.Remove(menuItem.InternalMenuItem);
			}

			if (e.Action == NotifyCollectionChangedAction.Add || e.Action == NotifyCollectionChangedAction.Replace || e.Action == NotifyCollectionChangedAction.Reset)
			{
				foreach (var button in e.NewItems.OfType<PageBarButton>().Where(item => item.IsVisible))
				{
					InsertApplicationBarItem(this.applicationBar.Buttons, button, b => b.InternalButton);
				}

				foreach (var menuItem in e.NewItems.OfType<PageBarMenuItem>().Where(item => item.IsVisible))
				{
					InsertApplicationBarItem(this.applicationBar.MenuItems, menuItem, m => m.InternalMenuItem);
				}
			}
		}

		private void InsertApplicationBarItem<T>(IList appBarList, T item, Func<T, object> getAppBarItem)
			where T : PageBarItem
		{
			var items = this.Items;

			var insertionIndex = 0;
			for (var i = items.IndexOf(item); i > 0; --i)
			{
				var prevButton = items[i - 1] as T;
				if (prevButton != null && prevButton.IsVisible)
				{
					insertionIndex = appBarList.IndexOf(getAppBarItem(prevButton)) + 1;
					break;
				}
			}

			appBarList.Insert(insertionIndex, getAppBarItem(item));
		}

		#endregion Items (DependencyProperty)

		#region Mode (DependencyProperty)

		public static readonly DependencyProperty ModeProperty =
			DependencyProperty.Register("Mode", typeof(ApplicationBarMode), typeof(PageBar),
			new PropertyMetadata(ApplicationBarMode.Default, new PropertyChangedCallback(OnModeChanged)));

		public ApplicationBarMode Mode
		{
			get { return (ApplicationBarMode)GetValue(ModeProperty); }
			set { SetValue(ModeProperty, value); }
		}

		protected virtual void OnModeChanged(DependencyPropertyChangedEventArgs e)
		{
			this.applicationBar.Mode = (ApplicationBarMode)e.NewValue;
		}

		private static void OnModeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			((PageBar)d).OnModeChanged(e);
		}

		#endregion Mode (DependencyProperty)

		#region IsVisible (DependencyProperty)

		public static readonly DependencyProperty IsVisibleProperty =
			DependencyProperty.Register("IsVisible", typeof(bool), typeof(PageBar),
			new PropertyMetadata(true, new PropertyChangedCallback(OnIsVisibleChanged)));

		public bool IsVisible
		{
			get { return (bool)GetValue(IsVisibleProperty); }
			set { SetValue(IsVisibleProperty, value); }
		}

		protected virtual void OnIsVisibleChanged(DependencyPropertyChangedEventArgs e)
		{
			this.applicationBar.IsVisible = (bool)e.NewValue;
		}

		private static void OnIsVisibleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			((PageBar)d).OnIsVisibleChanged(e);
		}

		#endregion IsVisible (DependencyProperty)

		#region Opacity (DependencyProperty)

		public static readonly DependencyProperty OpacityProperty =
					DependencyProperty.Register("Opacity", typeof(double), typeof(PageBar),
					new PropertyMetadata(1d, new PropertyChangedCallback(OnOpacityChanged)));

		/// <summary>
		/// A description of the property.
		/// </summary>
		public double Opacity
		{
			get { return (double)GetValue(OpacityProperty); }
			set { SetValue(OpacityProperty, value); }
		}

		protected virtual void OnOpacityChanged(DependencyPropertyChangedEventArgs e)
		{
			this.applicationBar.Opacity = (double)e.NewValue;
		}

		private static void OnOpacityChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			((PageBar)d).OnOpacityChanged(e);
		}

		#endregion Opacity (DependencyProperty)

		public PageBar()
		{
			this.applicationBar = new ApplicationBar();
			this.SetValue(ItemsProperty, new PageBarItemCollection());
		}

		internal void NotifyItemVisibilityChanged(PageBarItem item)
		{
			this.Dispatcher.BeginInvoke(delegate
			{
				this.Items.ReflectItemVisibility(item);
			});
		}
	}
}